Kopic je vec vrst, najveckrat pa se uporabljajo kar 
najpreprostejse med njimi -- dvojiske kopice (binary heaps).

Taksna kopica je v bistvu neke vrste drevo z zelo pravilno zgradbo:
notranja vozlisca imajo po dva otroka (razen mogoce enega, ki ima
enega samega otroka), listi so vsi na globini ali n ali pa n+1 in 
tisti, ki so na globini n+1, so vsi na levi strani drevesa.  

Mozne oblike kopice so torej taksne:

    o        o       o           o           o       
            o       o o        o   o       o   o     
                              o           o o       

     o          o              o                  o
  o     o    o     o       o       o          o       o
 o o   o    o o   o o    o   o   o   o      o   o   o   o
                        o                  o o

           o                         o
     o           o             o           o
  o     o     o     o       o     o     o     o
 o o   o                   o o   o o
 
 
           o                         o
     o           o             o           o
  o     o     o     o       o     o     o     o
 o o   o o   o             o o   o o   o o
 

           o                         o
     o           o             o           o
  o     o     o     o       o     o     o     o
 o o   o o   o o   o       o o   o o   o o   o o

in tako naprej.  Skratka, cim vemo, koliko vozlisc kopica ima,
je tudi cisto jasno, kaksne oblike bo.
                        
V vsakem vozliscu naj bo tudi neka vrednost, za te vrednosti 
pa mora veljati, da je vrednost posameznega vozlisca vecja ali 
enaka vrednostim v njegovih dveh otrocih.

Primer kopice z vrednostmi v vozliscih:

                  100
            __---^   ^--__
          95              90
        /    \          /    \
      27      50      80      30
     / \     / \      /
    20 10   28 25   35

Vrednost v korenu je torej vecja (ali enaka) od vrednosti v 
njegovih dveh otrocih, ti dve sta vecji (ali enaki) od vrednosti
v njunih otrocih, itd., itd.  Ta razmislek nam pokaze, da je 
vrednost v korenu najvecja v celem drevesu.  To je ravno lepo 
pri kopici -- omogoca nam, da imamo vedno pri roki najvecjo 
vrednost iz neke mnozice.  Obenem pa, ce se nam ta mnozica 
spreminja, je kopico mogoce zelo enostavno prilagajati tem
spremembam.

--

Zgornja kopica nam tako predstavlja mnozico {10, 20, 25, 27, 
28, 30, 35, 50, 80, 90, 95, 100} in nam tudi pove, da je 100
najvecje stevilo v tej mnozici.  Recimo, da bi hoteli zdaj, 
ko to vemo, s stevilom 100 nekaj narediti in ga vzeti ven iz
mnozice.  Kako naj popravimo kopico, da bo predstavljala novo
mnozico (torej brez stevila 100)?

Ideja je naslednja: vzemimo vrednost iz zadnjega (torej
skrajno desnega) lista in jo vpisimo v koren, tisti list pa 
zbrisimo:

                   35
            __---^   ^--__
          95              90
        /    \          /    \
      27      50      80      30
     / \     / \      
    20 10   28 25 

Vrednost, ki je zdaj v korenu, seveda najbrz ni dovolj velika,
da bi tam tudi ostala.  Tudi v nasem primeru je tako, saj
35 ni vecje ali enako 90 in 95.  Toda, pomislimo zdaj takole:
95 je bila prej gotovo najvecja vrednost levega poddrevesa,
90 pa najvecja vrednost desnega poddrevesa.  To velja se zdaj,
saj se levo poddrevo sploh ni spremenilo, desno pa ima celo eno
vozlisce manj (namrec bivsi list z vrednostjo 35); v vsakem
primeru torej vrednost, ki je bila prej najvecja, se vedno 
ostaja najvecja.  Najvecja vrednost celega drevesa pa je seveda
ali tista, ki je najvecja v levem poddrevesu korena, ali tista,
ki je najvecja v desnem poddrevesu korena, ali pa tista, ki je
v korenu.  V nasem primeru torej najvecja od 35, 95 in 90.
Vidimo, da bo najvecja vrednost celega drevesa 95, torej mora ta
priti v koren.  Vrednost, ki je bila trenutno v korenu, torej 35,
pa vpisimo v vozlisce, od koder smo dobili 95.

                   95
            __---^   ^--__
          35              90
        /    \          /    \
      27      50      80      30
     / \     / \      
    20 10   28 25 

Zdaj je 35 na novem polozaju in bi morala ustrezati pogoju, da je 
vecja od svojih dveh otrok, namrec 27 in 50, ce bi hotela tam tudi
ostati.  Z enakim razmislekom kot v prejsnjem odstavku se tudi tu
odlocimo, da je pametno zamenjati 35 in 50, se pravi spet z vecjim
od obeh otrok (kajti 27 je najvecje stevilo levega poddrevesa,
50 je najvecje stevilo desnega poddrevesa, najvecje stevilo celega
dela drevesa, ki se zacne pri 35, pa je torej najvecje izmed stevil
27, 50 in 35):

                   95
            __---^   ^--__
          50              90
        /    \          /    \
      27      35      80      30
     / \     / \      
    20 10   28 25 
 
Zdaj pa lahko ugotovimo, da je 35 vecja od svojih dveh otrok (se pravi
od 28 in 25), tako da lahko s postopkom koncamo.  Zdaj imamo spet 
pravo kopico, ki ustreza vsem pogojem iz definicije kopice in nam 
predstavlja mnozico {10, 20, 25, 27, 28, 30, 35, 50, 80, 90, 95}.

Lahko pa bi se zgodilo, da bi morali tudi v tem koraku izvesti 
zamenjavo; 35 bi se mogoce ustavila sele takrat, ko bi prisla v
kaksnega od listov (kajti list nima otrok in potem potem seveda 
trivialno ustreza pogoju, da mora biti vrednost v njem vecja od 
vrednosti v otrocih). 

Opisanemu postopku pravijo "pogrezanje" (sifting), ker se neka majhna
vrednost (v nasem primeru 35) pocasi pogreza navzdol po drevesu, 
dokler ne pride dovolj globoko.

--

Zdaj pa recimo, da bi hoteli v kopico dodati neko novo vrednost, 
recimo 92.  No, najbolj enostavno je dodati nekaj na konec kopice:

                   95
            __---^   ^--__
          50              90
        /    \          /    \
      27      35      80      30
     / \     / \     /
    20 10   28 25   92

Seveda pa nova vrednost mogoce ni tako majhna, da bi lahko ostala v 
tem listu.  Tudi v nasem primeru je tako -- 92 ni manjsa od 80.  Zato 
jo je pametno zamenjati z 80:

                   95
            __---^   ^--__
          50              90
        /    \          /    \
      27      35      92      30
     / \     / \     /
    20 10   28 25   80

Na novem polozaju se lahko vprasamo tako kot prej -- ali je 92 manjsa
(no, ali pa enaka) svojemu ocetu v drevesu, torej vrednosti 90?  Ni,
torej jo zamenjajmo z 90.  O smiselnosti tega se prepricamo takole:
90 je bila doslej najvecja vrednost v svojem poddrevesu, torej je bila
vecja od vsega v svojem levem poddrevesu (v nasem primeru: od 80) in
od vsega v svojem desnem poddrevesu (v nasem primeru: od 30).  Ce 
imamo zdaj pred sabo neko stevilo (v nasem primeru: 92), ki je se 
vecje, je torej to vecje tudi od vsega v levem in desnem poddrevesu, 
torej je 92 najvecje v celem poddrevesu, ki se zacne pri 90, in je 
zato prav, da pride v koren tega poddrevesa, torej na mesto, kjer je 
zdaj 90. Obenem pa ni nic narobe, ce pride 90 na mesto enega od 
svojih otrok, saj je, kot smo videli, vecja od vsega, kar je bilo po 
starem v enem in drugem poddrevesu, torej je cisto primerna za ta 
novi polozaj.

                   95
            __---^   ^--__
          50              92
        /    \          /    \
      27      35      90      30
     / \     / \     /
    20 10   28 25   80

Ce zdaj za novi polozaj stevila 92 spet pogledamo, ali ustreza pogojem
iz definicije kopice, ugotovimo, da to drzi -- 92 je res manjse ali 
enako 95.  Zato lahko s tem postopkom koncamo.  Spet imamo pravilno 
zgrajeno kopico, ki nam predstavlja mnozico {10, 20, 25, 27, 28, 30, 
35, 50, 80, 90, 92, 95}.

Temu postopku vcasih pravijo "dvigovanje" (lifting), ker se pac nova 
vrednost (v nasem primeru 92) dviguje navzgor po drevesu, dokler ne 
pride na primerno visino.

--

Se ena operacija, ki vcasih pride prav pri kopici, pa je ta, da 
enemu od vozlisc spremenimo vrednost.  Recimo, da bi hoteli v nasem 
primeru vrednost 27 spremeniti v 60.  

                   95
            __---^   ^--__
          50              92
        /    \          /    \
      60      35      90      30
     / \     / \     /
    20 10   28 25   80

Ker smo imeli prej pravilno zgrajeno kopico, je bila 27 pac vecja od 
svojih dveh otrok, torej v tem primeru od 20 in 10.  Ker se ji je 
vrednost povecala, to gotovo se vedno velja.  Pac pa je bila prej 
27 manjsa (ali enaka) od svojega starsa (torej od 50), kar pa zdaj po
novem ni vec nujno res.  In tudi v nasem primeru vidimo, da ni res.
Zdaj lahko storimo enako kot pri dodajanju novega vozlisca -- ce je
vrednost nekega vozlisca prevelika, jo moramo premikati navzgor.
V tem primeru bi zamenjali 60 in njenega starsa, torej 50:

                   95
            __---^   ^--__
          60              92
        /    \          /    \
      50      35      90      30
     / \     / \     /
    20 10   28 25   80

Zdaj lahko primerjamo 60 z novim starsem, torej s 95, in vidimo, da
je 60 manjse od 95, tako da je s kopico zdaj vse v redu.

Podobno ravnamo, ce hocemo nekemu vozliscu zmanjsati vrednost.  
Recimo, da bi hoteli vozliscu 92 zmanjsati vrednost na 5.  

                   95
            __---^   ^--__
          60              5
        /    \          /    \
      50      35      90      30
     / \     / \     /
    20 10   28 25   80

Ker je bila prej 92 manjsa od svojega starsa (torej od 95), je zdaj, 
ko se ji je vrednost se zmanjsala, gotovo se vedno manjsa od svojega
starsa.  Pac pa ni nujno se vedno vecja od svojih otrok (kot je bila 
prej) in v tem primeru jo moramo pogrezati.  Postopek je spet cisto 
tak, kot smo ga videli pri brisanju vozlisca.  Zamenjati moramo 5 in
vecjega od njenih dveh otrok:

                   95
            __---^   ^--__
          60              90
        /    \          /    \
      50      35       5      30
     / \     / \     /
    20 10   28 25   80

Na novem polozaju spet preverimo, ce je 5 zdaj ze vecja od svojih 
otrok (no, v tem primeru ima samo enega otroka) in ce ni, spet 
izvedemo zamenjavo:

                   95
            __---^   ^--__
          60              90
        /    \          /    \
      50      35      80      30
     / \     / \     /
    20 10   28 25   5 

Zdaj pa 5 otrok sploh nima vec in torej vsekakor ustreza pogoju, da je
vecja ali enaka svojim otrokom, zato je pogrezanja konec in vemo, da
imamo spet dobro urejeno kopico.

Glede spreminjanja vrednosti vozlisc je potrebno poudariti se, da 
moramo nekako vedeti, _kateremu_ vozliscu zelimo spremeniti vrednost.
Vozlisca namrec v kopici ni enostavno najti po vrednosti, saj 
vrednosti v kopici niso tako lepo urejene.  Na primer: recimo, da bi 
hoteli spreminjati vozlisce z vrednostjo 30.  Kako naj vemo, kje v
kopici se nahaja?  Ko pogledamo koren in njegova otroka, vidimo 60
in 90; 30 je manjse od obeh, torej bi bilo lahko v katerem koli 
poddrevesu.  Ce gremo potem v levo poddrevo, vidimo 50 in 35 in spet
bi bilo lahko 30 pod katerim koli od teh dveh.  Skratka, lahko se
zgodi, da bi morali na ta nacin preiskati kar velik del drevesa, 
preden bi nasli to, kar nas zanima.  Moramo torej imeti nek
drug nacin, kako dolociti, katero vozlisce bi radi spreminjali.
To obicajno pomeni, da morajo imeti vozlisca se kaksne druge oznake,
prek katerih jih lahko identificiramo, mi pa moramo ob premikanju
vrednosti po kopici vzdrzevati neko kazalo, ki pove, kje v kopici se
nahaja posamezen element.  Kasneje bomo videli kak konkreten primer 
tega.

--

Lepo pri kopici je, da je zaradi njene izredno pravilne zgradbe ni 
treba implementirati s kazalci in podobnimi recmi, kot je sicer navada
pri drevesih.  Lahko jo hranimo kar v navadni tabeli!  Recimo, da
bi vozlisca v kopici ostevilcili od 0 naprej, in sicer od zgoraj 
navzdol in na vsakem nivoju od leve proti desni:

                   0
            __---^   ^--__
          1               2 
        /    \          /    \
      3       4       5       6 
     / \     / \     /
    7   8   9  10  11

Vse, kar potrebujemo, da se bomo lahko sprehajali po kopici, je to, 
da znamo od dolocenega vozlisca priti do njegovih dveh otrok in do
njegovega starsa.  No, ce malce pogledamo tole ostevilcenje vozlisc
na zadnji sliki, lahko opazimo naslednje pravilo:

  vozlisce z indeksom i ima otroka 2*i + 1 in 2*i + 2
  ter starsa (i - 1) div 2.

Ce je kdo do tega pravila nezaupljiv, se lahko o njem preprica tudi
s taksnim razmislekom: to, da je neko vozlisce na indeksu i, pomeni,
da je pred njim v nasem ostevilcenju se i drugih vozlisc.  Pred
njegovima otrokoma so torej otroci vseh teh i vozlisc, teh pa je 
seveda 2*i, saj ima vsako vozlisce dva otroka.  Poleg tega pa je
pred njegovima otrokoma se koren, ki v teh 2*i ni bil stet, saj ni
koren otrok nobenega vozlisca.  Pred otrokoma je torej 2*i + 1
vozlisc, torej ima prvi otrok indeks 2*i + 1, drugi pa zato seveda 
2*i + 2.  Do formule za starsa lahko potem pridemo na primer takole:
naj bo j indeks vozlisca, ki nas zanima.  Ce je j lih, je oblike
j = 2*i + 1 in potem po prejsnji formuli vidimo, da je j-jev oce
ravno i, torej lahko to indeksa oceta pridemo po formuli (j - 1)/2.
Ce pa je j sod, je oblike j = 2*i + 2 in je njegov oce tudi ravno i;
ce uporabimo kar isto formulo, (j - 1)/2, pa dobimo i + 1/2.  Ce torej
vrednosti (j - 1)/2 zaokrozimo navzdol, bomo v obeh primerih dobili
ravno i.  Natancno to pa naredi operator div: tako smo prisli do
formule (j - 1) div 2.

Enostavno je tudi preveriti, ali je neko vozlisce ze list ali pa je se
notranje vozlisce.  Recimo, da ima kopica n vozlisc, torej gredo 
njihovi indeksi od 0 do n-1.  Zadnje notranje vozlisce je tisto, ki
ima kot otroka vozlisce n-1.  To pa je, kot nam pove ena od
gornjih formul, vozlisce ((n-1)-1) div 2, torej (n-2) div 2, kar
je isto kot (n div 2)-1.  Notranja vozlisca so torej tista na indeksih
od 0 do (n div 2)-1, ostalo pa so listi.

Razmislimo se malo o casovni zahtevnosti operacij na kopici.  Videli
smo, da je na vsakem nivoju dvakrat toliko vozlisc kot na prejsnjem:
na prvem nivoju je eno samo vozlisce (koren), na drugem nivoju sta dve
(korenova otroka), na tretjem so stiri, na cetrtem jih je osem, na
petem sestnajst in tako naprej.  No, izjema je le zadnji nivo, ki 
mogoce ni cisto poln (gotovo pa vsebuje vsaj eno vozlisce).  Torej, 
ce ima kopica h nivojev, ima vsaj 
  1 + 2 + 4 + 8 + ... + 2^{h-2} + 1 vozlisc (kar je ravno 2^{h-1}) 
in najvec 1 + 2 + ... + 2^{h-2} + 2^{h-1} vozlisc (kar pa je ravno
2^h - 1).  Oznacimo stevilo vozlisc z n, pa smo dobili ugotovitev: 
  2^{h-1} <= n < 2^h.
Ce zdaj v tej neenacbi izvedemo dvojiski logaritem:
  h-1 <= lg n < h,
vidimo, da lahko globino drevesa, h, izrazimo v odvisnosti od stevila
vozlisc kot h = floor(lg n) + 1 ("floor" predstavlja zaokrozevanje
navzdol).  To pa pomeni, da je globina drevesa sorazmerna z logaritmom
stevila vozlisc.  Operacije na kopici pa so, kot smo videli, vse 
taksne, da se v vsakem koraku premaknemo za en nivo dol (pri 
pogrezanju) ali dol (pri dvigovanju vozlisca), torej je njihova 
casovna
zahtevnost v najslabsem primeru sorazmerna z globino kopice -- 
casovna zahtevnost teh operacij je torej O(log n).

--

Se ena koristna operacija pri kopici je, ce ze imamo neko tabelo z 
nekimi n elementi, pa bi jo radi uredili v kopico.  Seveda bi lahko 
naredili tako, da za zacetek ustanovimo kopico z enim samim 
elementom, nato pa vse ostale dodamo vanjo po zgoraj opisanem 
postopku 
za dodajanje v kopico.  Toda vsako dodajanje traja O(log n) casa,
vseh dodajanj pa bo n (oziroma n-1), torej bo skupna poraba casa
v najslabsem primeru O(n log n).  (Saj je res, da bo od zacetka, ko
kopica se ni tako globoka, dodajanje malo hitrejse; toda ne pozabimo,
da je kar pol elementov kopice listov in ko bomo dodajali te, bo
kopica ze imela globino O(log n).)  Izkaze se, da lahko to storimo 
tudi hitreje.  Recimo, da imamo tabelo 
  60 5 28 90 25 80 35 50 20 10 95 30.  Ce nanjo pogledamo kot na 
kopico, dobimo:

                   60
            __---^   ^--__
          5               28
        /    \          /    \
      90      25      80      35
     / \     / \     /
    50 20   10  95  30

Zdaj je ideja v tem, da gremo od konca proti zacetku (torej: od spodaj
navzgor, od desne proti levi) in po potrebi vsak element pogreznemo.
Listov seveda ni treba pogrezati, ker nimajo otrok in so torej gotovo
vecji od vseh svojih otrok.  Zacnimo torej pri zadnjem notranjem 
vozliscu, kar je v nasem primeru 80.  Ta ima otroka 30 in ker je 30 
manjse od 80, je tu vse v redu.  Naslednje notranje vozlisce je 25, 
ki ga primerjamo s 95 in 5 in vidimo, da ga je treba zamenjati z 95:

                   60
            __---^   ^--__
          5               28
        /    \          /    \
      90      95      80      35
     / \     / \     /
    50 20   10  20  30

Nato vidimo, da z 90 ni treba narediti nic, ker je 90 vecje od 
50 in 20.  Potem pridemo do 28 in vidimo, da ni vecja od 80 in 35,
zato jo bo treba pogrezniti:

                   60
            __---^   ^--__
          5               80
        /    \          /    \
      90      95      28      35
     / \     / \     /
    50 20   10  20  30

Tudi na svojem novem polozaju 28 se ni dobra, zato jo bo treba
pogrezniti se enkrat:

                   60
            __---^   ^--__
          5               80
        /    \          /    \
      90      95      30      35
     / \     / \     /
    50 20   10  20  28

Potem lahko nadaljujemo, kjer smo prej koncali, torej se zdaj
lotimo vozlisca 5.  To ni vecje od 90 in 95 in ga bo torej tudi
treba pogrezati:

                   60
            __---^   ^--__
          95              80
        /    \          /    \
      90      5       30      35
     / \     / \     /
    50 20   10  20  28

                   60
            __---^   ^--__
          95              80
        /    \          /    \
      90      20      30      35
     / \     / \     /
    50 20   10  5   28

Koncno se lotimo se korena, 60, ki ga bo tudi treba pogrezati,
ker ni vecje od 95 in 80:

                   95
            __---^   ^--__
          60              80
        /    \          /    \
      90      20      30      35
     / \     / \     /
    50 20   10  5   28

                   95
            __---^   ^--__
          90              80
        /    \          /    \
      60      20      30      35
     / \     / \     /
    50 20   10  5   28

S tem je stvar opravljena, vrednosti 60 ni treba pogrezati se
globlje, mi pa imamo cisto pravilno urejeno kopico.  O 
pravilnosti se lahko prepricamo takole: ker smo sli od spodaj
navzgor, je imelo vsako vozlisce, ko smo koncno prisli do njega, 
za svoji poddrevesi ze cisto pravilno zgrajeni kopici in s tem, 
ko smo ga po potrebi pogrezali, smo zdaj iz tistih dveh 
poddreves in vozlisca nad njima naredili pravilno urejeno kopico
z enim nivojem vec.  Ko pridemo do konca, je tudi cela kopica
pravilno urejena.

Ucinkovitost tega postopka pa je v tem, da vecina pogrezanj ne
more iti prav globoko.  Listov (ki predstavljajo kar polovico vseh
vozlisc), kot smo videli, sploh ni treba pogrezati.  Nato pridejo 
vozlisca en nivo nad listi, ki jih je priblizno n/4; teh ocitno
ne bo treba pogrezati globlje kot en nivo, saj se globlje sploh
ne da iti.  Nad njimi je priblizno n/8 vozlisc, ki jih bo treba
v najslabsem primeru pogrezati dva nivoja globoko in tako naprej.
Recimo zaradi lazje izpeljave, da je n = 2^h - 1.  Potem je
kopica sestavljena iz h polnih nivojev in ce zdaj sestejemo,
koliko dela imamo v naslabsem primeru, vidimo:
  2^{h-1} je listov, ki jih sploh ne bomo pogrezali;
  2^{h-2} pogrezanj za najvec 1 nivo;
  2^{h-3} pogrezanj za najvec 2 nivoja;
  2^{h-4} pogrezanj za najvec 3 nivoje;
  .....
  2^1 pogrezanj za najvec h-2 nivojev;
  2^0 pogrezanj za najvec h-1 nivojev (to je za koren, nato pa 
nehamo).  Ce zdaj vse to sestejemo:
  1 * 2^{h-2} + 2 * 2^{h-3} + 3 * 2^{h-4} + ... + (h-2) * 2 + (h-1),
se da pokazati, da je ta vsota (ce se nisem kje zmotil) enaka
  2^h - h - 1.
To pa je enako n - h, kar je <= n.  Se pravi: pri n elementih je
kolicina dela omejena navzgor z n.  No, ce n ne bi bil oblike 2^h-1,
bi se vedno lahko rekli, da imamo pri takem n gotovo najvec toliko
dela, kot ce bi vzeli naslednji n, ki je take oblike, ta pa gotovo
ni vec kot dvakrat vecji od nasega dejanskega n, zato bi bila 
kolicina dela v najslabsem primeru omejena navzgor z 2n.
Kakorkoli ze, saj ti detajli niso tako zelo pomembni.  Pomembno je,
da smo ugotovili, da porabi opisani postopek gradnje kopice le O(n)
casa, ne pa O(n log n) kot prej omenjeni naivnejsi pristop.

--

Ce boste kdaj implementirali kopice, vam svetujem, da dobro pazite
in jih temeljito pretestirate, ker se meni menda se ni zgodilo, da
ne bi imel v kodi za delo s kopico kaksne napake. :)

const Max = ...;
type
  TElement = ...;
  THeap = record
    a: array[1..Max] of TElement;
    n: Integer;
  end;

procedure Swap(var E1, E2: TElement);
var T: TElement; 
begin 
  T := E1; E1 := E2; E2 := T; 
end;

procedure Sift(var H: THeap; i: Integer);
var ci: Integer;
begin
  while i < H.n div 2 do (* = dokler i ne pride v list *)
    begin
    ci := 2 * i + 1;
    (* ce ima i dva otroka, preverimo, kateri je vecji *)
    if ci + 1 < H.n then
      if H.a[ci + 1] > H.a[ci] then ci := ci + 1;
    (* na tem mestu je ci indeks tistega i-jevega otroka,
       ki ima najvecjo vrednost *)
    if H.a[i] >= H.a[ci] then 
      (* i je vecji od svojih otrok, koncajmo s pogrezanjem *)
      Exit;
    (* zamenjamo i in vecjega od obeh otrok *)
    Swap(H.a[i], H.a[ci]);
    i := ci;
    end;
end;   

procedure Lift(var H: THeap; i: Integer);
begin
  while i > 0 do
    begin
    (* pi naj bo indeks i-jevega starsa *)
    pi := (i - 1) div 2;
    (* ce je pi vecji od i, i-ja ni treba se naprej dvigovati *)
    if H.a[pi] >= H.a[i] then Exit;
    (* zamenjamo i in njegovega starsa *)
    Swap(H.a[i], H.a[pi]);
    i := pi;
    end;
end;

procedure Add(var H: THeap; NewElement: TElement);
begin
  H.a[n] := NewElement;
  H.n := H.n + 1;
  Lift(H, H.n - 1);
end;

procedure DeleteRoot(var H: THeap; var DeletedElement: TElement);
begin
  DeletedElement := H.a[0];
  H.a[0] := H.a[n-1];
  H.n := H.n - 1;
  Sift(H, 0);
end;

(* spremeni vrednost elementa H.a[i] na NewValue in ga
   po potrebi pogrezne ali dvigne *)
procedure ChangeValue(var H: THeap; i: Integer; NewValue: TElement);
begin
  if H.a[i] < NewValue then
    begin
    (* Vrednost i-tega elementa se bo povecala, zato ga bo
       treba dvigovati. *)
    H.a[i] := NewValue;
    Lift(H, i);
    end
  else if H.a[i] > NewValue then
    begin
    (* Vrednost se bo zmanjsala -- treba bo pogrezati. *)
    H.a[i] := NewValue;
    Sift(H, i);
    end;
end;

procedure InitHeap(var H: THeap);
begin
  H.n := 0;
end;

(* predpostavimo, da ima H.n ze neko smiselno vrednost,
   v H.a[0..H.n-1] pa so elementi, ki bi jih zdaj radi
   preuredili v kopico *)
procedure Heapify(var H: THeap);
begin
  for i := (H.n div 2) - 1 downto 0 do
    Sift(H, i);
end;

[Opomba: gornja koda se dela, da lahko elemente primerja kar
z operatorji <, >, <= in >=.  Ce so elementi neke preproste
zadeve, npr. integerji, je to seveda v redu, drugace pa bi morali
napisati kaksno posebno kodo za primerjanje elementov (mogoce 
kar poseben podprogram, ki bi povedal, ali je en element vecji
od drugega ali obratno ali pa sta enaka).]

Kot kaze ze ta implementacija, bi bila kopica tudi odlicen kandidat
za objektno orientiran pristop.  Gornjo implementacijo bi lahko
izboljsali se na primer tako, da bi bila tabela a dinamicno alocirana
in bi jo AddNew povecal, ce bi bila ze pred dodajanjem novega
elementa cisto polna.

Jasno je tudi, da bi lahko pri kopici uporabili kaksen drugacen
vrstni red.  Doslej smo rekli, da mora biti vrednost vozlisca vecja
ali enaka kot v njegovih otrocih -- prav tako bi lahko rekli, da mora
biti manjsa ali enaka ali pa bi uporabili se kak drug vrstni red.
To je pac odvisno od tega, kaksen element zelimo imeti pri roki v
korenu kopice.  Pri doslej opisani implementaciji imamo pri roki
najvecji element, ce bi hoteli imeti pri roki najmanjsega, pa bi
morali uvesti pogoj, da je vrednost vozlisca manjsa kot v njegovih
otrocih.

Ce bi hoteli vzdrzevati kazalo o tem, kje v kopici je kak element
(tu pac predpostavimo, da imajo elementi se neke enolicne oznake,
ki se ne spreminjajo), bi morali v tem kazalu popravljati vrednosti 
ob dodajanju v kopico in ob premikih pri pogrezanju in dvigovanju
(no, pa naceloma se ob brisanju).

Lahko bi uvedli tudi brisanje poljubnega elementa iz kopice (ce
poznamo njegov indeks).  Postopek bi bil enak kot pri DeleteRoot,
le da bi namesto indeksa 0 pac uporabili nek poljuben indeks i
in potem vanj vpisali zadnji element.  Razlika pa bi bila v tem,
da ni nujno, da bi morali potem ta element pogrezati; mogoce bi
ga morali dvigovati.

--

Mimogrede, v zvezi s kopico se pogosto pojavlja se en izraz: 
"vrsta s prioriteto" ali "prioritetna vrsta" (priority queue).
Spomnimo se, da je navadna vrsta (queue) podatkovna struktura,
v katero lahko dodajamo elemente, iz nje pa jih dobivamo v 
taksnem vrstnem redu, ko smo jih dodajali (FIFO = first in, 
first out).  Obicajno to implementirajo s seznamom ali pa tabelo
(po moznosti s tako, ki si jo predstavljamo kot krog -- 
ring buffer).  No, vcasih pa zelimo, da elementi iz vrste ne bi
prihajali v takem vrstnem redu, v kakrsnem smo jih dodajali
vanjo, ampak bi imel vsak element neko svojo prioriteto in bi
prisel iz vrste vsakic tisti element, ki bi imel izmed vseh v
vrsti najvisjo prioriteto.  (Nekateri se nad izrazom "vrsta s
prioriteto" razburjajo, ces da to potem sploh ni nikakrsna 
vrsta vec.  Kljub temu se ta izraz kar veliko uporablja.)  Kopica
je kot nalasc za implementacijo prioritetne vrste, saj so elementi
z velikimi vrednostmi v kopici na vrhu in ce uporabimo za 
vrednost elementa kar njegovo prioriteto, bomo imeli v korenu
ravno element z najvisjo prioriteto.  Brisanje korena (podprogram
DeleteRoot) potem ustreza ravno temu, da iz vrste vzamemo
element z najvisjo prioriteto.

-- 

Za konec lahko omenimo se, da obstajajo tudi druge vrste kopic,
ki so vecinoma vse namenjene implementaciji prioritetne vrste.
Poleg ocitne moznosti, da bi namesto dvojiske kopice vzeli
trojisko, stirisko ipd., obstajajo na primer se binomske kopice, 
Fibonaccijeve kopice, itd.  Te nove vrste kopic so sicer v nekem
smislu lahko ucinkovitejse, vendar so tudi bolj zapletene in imajo
vec overheada, tako da se v praksi ne uporabljajo kaj dosti (smisel
bi imele mogoce le pri res zelo velikem stevilu operacij).